package bll

import (
	"bytes"
	"context"
	"encoding/binary"
	"fmt"
	"gitlab.local/backend/proto/dto"
	"hash/crc32"
	log "signal/collector/logger"
	"signal/lib/trace_log"
	"signal/model"
	"signal/utils"
)

var Upgrade = &upgrade{}

// 服务端向设备发起升级
type upgrade struct{}

type PacketBSC22 struct {
	Magic       uint16
	Len         uint16
	ID          uint64
	DeviceType  uint8
	Cmd         uint8
	BindAddress uint32
	BindLength  uint32
	Body        []byte
	Crc32       uint32
}

func Compare(b1, b2 *PacketBSC22) (bool, error) {

	if b1.Magic != b2.Magic {
		return false, fmt.Errorf("magic mismatch")
	}
	if b1.Len != b2.Len {
		return false, fmt.Errorf("length mismatch")
	}
	if b1.ID != b2.ID {
		return false, fmt.Errorf("id mismatch")
	}
	if b1.DeviceType != b2.DeviceType {
		return false, fmt.Errorf("device_type mismatch")
	}
	if b1.Cmd != b2.Cmd {
		return false, fmt.Errorf("cmd mismatch")
	}
	if b1.BindAddress != b2.BindAddress {
		return false, fmt.Errorf("bind_address mismatch")
	}
	if b1.BindLength != b2.BindLength {
		return false, fmt.Errorf("bind_length mismatch")
	}
	if !bytes.Equal(b1.Body, b2.Body) {
		return false, fmt.Errorf("body mismatch")
	}
	if b1.Crc32 != b2.Crc32 {
		return false, fmt.Errorf("crc32 mismatch")
	}
	return true, nil
}

func minLength() int {
	return 2 + 2 + 8 + 1 + 1 + 4 + 4 + 4
}

func CheckCrc(b []byte) bool {

	crcCalc := binary.LittleEndian.Uint32(b[len(b)-4:])
	return crc32.ChecksumIEEE(b[:len(b)-4]) == crcCalc
}

func BytesToObject(b []byte) interface{} {

	if len(b) < minLength() {
		return nil
	}

	var body []byte
	if len(b) > minLength() {
		bodyLen := len(b) - minLength()
		body = b[22 : 22+bodyLen]
	}

	p := &PacketBSC22{
		Magic:       binary.LittleEndian.Uint16([]byte{b[0], b[1]}),
		Len:         binary.LittleEndian.Uint16([]byte{b[2], b[3]}),
		ID:          binary.LittleEndian.Uint64([]byte{b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11]}),
		DeviceType:  b[12],
		Cmd:         b[13],
		BindAddress: binary.LittleEndian.Uint32([]byte{b[14], b[15], b[16], b[17]}),
		BindLength:  binary.LittleEndian.Uint32([]byte{b[18], b[19], b[20], b[21]}),
		Body:        body,
		Crc32:       binary.LittleEndian.Uint32([]byte{b[len(b)-4], b[len(b)-3], b[len(b)-2], b[len(b)-1]}),
	}
	return p
}

/*
		magic	  length	id	  device	cmd	  bin_address	  bin_length	data	crc32
	  2Byte	  2Byte	 8Byte	1Byte	  1Byte	  4Byte	       4Byte	  nByte	  4Byte
*/

/*
		magic	  length	id	  device	cmd	  bin_address	  bin_length	data	crc32
	  2Byte	  2Byte	 8Byte	1Byte	  1Byte	  4Byte	       4Byte	  nByte	  4Byte
*/
func BuildPacket(bsc22 *dto.UpgradeBSC22Data) *PacketBSC22 {

	p := &PacketBSC22{
		Magic:       binary.BigEndian.Uint16([]byte{0x5a, 0xa5}),
		ID:          bsc22.Id,
		DeviceType:  uint8(bsc22.DeviceType),
		Cmd:         uint8(bsc22.UpgradeCmd),
		BindLength:  bsc22.BindLength,
		BindAddress: bsc22.BindAddress,
		Len:         uint16(8) + uint16(1) + uint16(1) + uint16(4) + uint16(4) + uint16(len(bsc22.Body)) + uint16(4),
		Body:        bsc22.Body,
	}

	crc32Data := p.ToHexBytes(true)
	p.Crc32 = crc32.ChecksumIEEE(crc32Data)
	return p
}

func (p *PacketBSC22) ToHexBytes(args ...any) []byte {

	var (
		crcCalculate bool
		buffer                        = bytes.Buffer{}
		order        binary.ByteOrder = binary.LittleEndian
	)
	if len(args) == 1 {
		crcCalculate = args[0].(bool)
	}

	_ = binary.Write(&buffer, order, p.Magic)
	_ = binary.Write(&buffer, order, p.Len)
	_ = binary.Write(&buffer, order, p.ID)
	_ = binary.Write(&buffer, order, p.DeviceType)
	_ = binary.Write(&buffer, order, p.Cmd)
	_ = binary.Write(&buffer, order, p.BindAddress)
	_ = binary.Write(&buffer, order, p.BindLength)
	_ = binary.Write(&buffer, binary.LittleEndian, p.Body)
	if !crcCalculate {
		_ = binary.Write(&buffer, order, p.Crc32)
	}

	return buffer.Bytes()
}

func (u *upgrade) init() func() {
	return func() {}
}

/*
BSC21开头的SN，升级指令使用CloudLink-01中的0x32,其他指令也使用CloudLink-01
BSC22开头的SN，升级指令使用OTA协议0x5aa5, 其他指令用CloudLink-01
*/

func (u *upgrade) OTA(ctx context.Context, in *dto.UpgradeRequest) error {
	var (
		err  error
		sn   = in.Sn
		id   uint8
		addr = []byte(in.Addr)
	)
	// add end of string
	addr = append(addr, 0)

	defer func() {
		trace_log.DebugLogger(in.Sn, "OTA 执行 data:", utils.ToJsonString(in), " err:", err)
	}()

	if _, id, err = Session.GetRequestID(sn, model.CmdUpgrade); err != nil {
		log.Error("get request error ", err)
		return err
	}
	packet := model.BuildPacket(model.CmdUpgrade, id, addr)

	if err = Session.Send(sn, packet.ToHexBytes()); err != nil {
		log.Error("send OTA command error: ", err)
		return err
	}
	log.Info("send OTA command success")
	return nil
}

func (u *upgrade) OTABSC22(ctx context.Context, in *dto.UpgradeBSC22Data) error {
	var (
		err       error
		sn        = in.Sn
		sendBytes = BuildPacket(in).ToHexBytes()
	)

	defer func() {
		trace_log.DebugLogger(in.Sn, "OTABSC22 执行 data:", fmt.Sprintf("%X", sendBytes), " err:", err)
	}()

	if err = Session.Send(sn, sendBytes); err != nil {
		log.Error("send OTABSC22 command error: ", err)
		return err
	}
	log.Info("send OTABSC22 command success")
	return nil
}
